﻿using System.Collections.Generic;
using System.Web.UI;
using System;

namespace PropertyExpression.TypedDataBinding.Web
{
    //TODO: переименовать
    public abstract class CollectionBinder<TData, TBaseControl> : IGroupBinder<TData>, IControlBinder<TBaseControl> where TBaseControl : Control
    {
        private readonly GroupBinder groupBinder;

        protected CollectionBinder()
        {
            groupBinder = new GroupBinder(this);
        }

        private class GroupBinder : GroupBinderBase<TData>
        {
            private readonly CollectionBinder<TData, TBaseControl> parent;
            public GroupBinder(CollectionBinder<TData, TBaseControl> parent)
            {
                this.parent = parent;
            }

            private readonly Dictionary<TBaseControl, Dictionary<object, Dictionary<Control, IControlBinder>>> dictionary =
                new Dictionary<TBaseControl, Dictionary<object, Dictionary<Control, IControlBinder>>>();

            protected override IEnumerable<KeyValuePair<Control, IControlBinder>> GetCurrentDictionary()
            {
                Dictionary<object, Dictionary<Control, IControlBinder>> subDictionary;
                if (dictionary.TryGetValue(GetBaseControl(ParentControl), out subDictionary))
                {
                    Dictionary<Control, IControlBinder> subSubDictionary;
                    if (subDictionary.TryGetValue(GetDataContainer(ParentControl), out subSubDictionary))
                    {
                        return subSubDictionary;
                    }
                    else
                    {
                        return emptyDictionary;
                    }
                }
                else
                {
                    return emptyDictionary;
                }
            }

            private static readonly Dictionary<Control, IControlBinder> emptyDictionary
                = new Dictionary<Control, IControlBinder>();

            protected override void AddToCashe(Control control, IControlBinder controlBinder)
            {
                Dictionary<object, Dictionary<Control, IControlBinder>> subDictionary;
                var baseControl = GetBaseControl(control.Parent);
                if (dictionary.TryGetValue(baseControl, out subDictionary) == false)
                {
                    subDictionary = new Dictionary<object,Dictionary<Control,IControlBinder>>();
                    dictionary.Add(baseControl, subDictionary);
                }
                Dictionary<Control, IControlBinder> subSubDictionary;
                object dataItemContainer = GetDataContainer(control.Parent);
                if (subDictionary.TryGetValue(dataItemContainer, out subSubDictionary) == false)
                {
                    subSubDictionary = new Dictionary<Control, IControlBinder>();
                    subDictionary.Add(dataItemContainer, subSubDictionary);
                }
                subSubDictionary.Add(control, controlBinder);
            }

            private object GetDataContainer(Control control)
            {
                //TODO: переписать через цикл
                if (parent.IsDataContainer(control))
                {
                    return control;
                }
                else
                {
                    return GetDataContainer(control.Parent);
                }
            }

            private static TBaseControl GetBaseControl(Control control)
            {
                //TODO: переписать через цикл
                if (control is TBaseControl)
                {
                    return (TBaseControl)control;
                }
                else
                {
                    return GetBaseControl(control.Parent);
                }
            }

            internal void Clear()
            {
                dictionary.Remove(parent.currentControl);
            }

            internal void SetParent(Control dataItemControl)
            {
                if (dictionary[GetBaseControl(dataItemControl.Parent)].ContainsKey(dataItemControl))
                {
                    ParentControl = dataItemControl;                    
                }
                else
                {
                    SetParent(dataItemControl.Parent);
                }
            }
        }

        protected abstract bool IsDataContainer(Control control);

        public void Add<TControl>(TControl control, IControlBinder<TControl> controlBinder) where TControl : Control
        {
            groupBinder.Add(control, controlBinder);
        }

        public TData Data
        {
            get
            {
                return groupBinder.Data;
            }
        }

        public Func<TData> DataFunc 
        {
            get
            {
                return groupBinder.DataFunc;
            }
            set
            {
                groupBinder.DataFunc = value;
            }
        }

        public void FromModel()
        {
            groupBinder.FromModel();
        }

        public Control ParentControl
        {
            get
            {
                return groupBinder.ParentControl;
            }
            set
            {
                groupBinder.ParentControl = value;
            }
        }

        public void ToModel()
        {
            groupBinder.ToModel();
        }

        public void FromModel(TBaseControl control)
        {
            DataSource.IfNotNull(() =>
               {
                   BaseControlWrapper controlWrapper = CreateBaseControlWrapper(control);
                   controlWrapper.ItemDataBound += controlWrapper_ItemDataBound;
                   controlWrapper.ItemCreated += controlWrapper_ItemCreated;
                   controlWrapper.DataSource = DataSource();
                   currentControl = control;
                   controlWrapper.DataBind();
                   currentControl = null;
                   controlWrapper.ItemDataBound -= controlWrapper_ItemDataBound;
                   controlWrapper.ItemCreated -= controlWrapper_ItemCreated;
               });
        }

        private TBaseControl currentControl;

        private void controlWrapper_ItemCreated(IBaseControlItemEventArgs e)
        {
            if (e.ItemIndex == 0)
            {
                groupBinder.Clear();
            }
            DataFunc = () => (TData)e.DataItem;
        }

        private void controlWrapper_ItemDataBound(IBaseControlItemEventArgs e)
        {
            ParentControl = e.Item;
            FromModel();
        }

        protected abstract BaseControlWrapper CreateBaseControlWrapper(TBaseControl control);

        protected abstract class BaseControlWrapper
        {
            public abstract event Action<IBaseControlItemEventArgs> ItemDataBound;

            public abstract event Action<IBaseControlItemEventArgs> ItemCreated;

            public abstract object DataSource { set; }

            public abstract void DataBind();

            protected Action<IBaseControlItemEventArgs> ItemCreatedAction { get; private set; }

            protected Action<IBaseControlItemEventArgs> ItemDataBoundAction { get; private set; }

            protected void AddItemCreatedHandler(Action<IBaseControlItemEventArgs> value, Action action)
            {
                ItemCreatedAction = value;
                action();
            }

            protected void RemoveItemCreatedHandler(Action<IBaseControlItemEventArgs> value, Action action)
            {
                action();
                ItemCreatedAction = null;
            }

            protected void AddItemDataBoundHandler(Action<IBaseControlItemEventArgs> value, Action action)
            {
                ItemDataBoundAction = value;
                action();
            }

            protected void RemoveItemDataBoundHandler(Action<IBaseControlItemEventArgs> value, Action action)
            {
                action();
                ItemDataBoundAction = null;
            }
        }

        protected interface IBaseControlItemEventArgs
        {
            Control Item { get; }

            object DataItem { get; }

            int ItemIndex { get; }
        }

        public void ToModel(TBaseControl control)
        {
            //no-op
        }

        public Func<IEnumerable<TData>> DataSource { private get; set; }

        public void SetParent(Control dataItemControl)
        {
            groupBinder.SetParent(dataItemControl);
        }
    }
}
